﻿#region Licence
// <copyright file="Helpers/Extensions.cs" company="Kopigi">
// Copyright © Kopigi 2014
// </copyright>
// ****************************************************************************
// <author>Marc PLESSIS</author>
// <date>11/10/2014</date>
// <project>Nentang.wpf</project>
// <web>http://nentang.codeplex.com</web>
// <license>
// The MIT License (MIT)
// 
// Copyright (c) 2013 Marc Plessis
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 
// </license>
#endregion
#region using

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

#endregion

namespace Helpers.Extensions
{
    /// <summary>
    /// Classe d'extensions
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Permet de renvoyer une liste purgée de ces éventuels éléments en doublon
        /// </summary>
        /// <typeparam name="T">Type des éléments de la liste</typeparam>
        /// <param name="list">Liste d'éléments</param>
        /// <returns>Liste expurgée de ces événetuels doublons</returns>
        public static List<T> WithoutDuplicates<T>(this List<T> list) where T : class
        {
            var listReturn = new List<T>();
            foreach (var element in list)
            {
                if (!listReturn.Contains(element))
                {
                    listReturn.Add(element);
                }
            }
            return listReturn;
        }

        /// <summary>
        /// Permet de renvoyer une liste purgée de ces éventuels éléments en doublon
        /// </summary>
        /// <typeparam name="T">Type des éléments de la liste</typeparam>
        /// <param name="list">Liste d'éléments</param>
        /// <returns>Liste expurgée de ces événetuels doublons</returns>
        public static List<T> WithoutDuplicatesStruct<T>(this List<T> list) where T : struct
        {
            var listReturn = new List<T>();
            foreach (var element in list)
            {
                if (!listReturn.Contains(element))
                {
                    listReturn.Add(element);
                }
            }
            return listReturn;
        }

        private static readonly Action EmptyDelegate = delegate() { };

        /// <summary>
        /// Force l'update d'un UIElement
        /// </summary>
        /// <param name="uiElement"><see cref="UIElement"/> dont il faut forcer le rafraichissement</param>
        public static void Refresh(this UIElement uiElement)
        {
            uiElement.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, EmptyDelegate);
        }

        [DllImport("gdi32")]
        static extern int DeleteObject(IntPtr o);

        /// <summary>
        /// Permet d'éxécuter du code en mode STA (Single Thread Appartment)
        /// </summary>
        /// <param name="app"><see cref="Application"/> courante</param>
        /// <param name="action"><see cref="Action"/> à executer</param>
        public static void ExecOnUiThread(this Application app, Action action)
        {
            var dispatcher = app.Dispatcher;
            if (dispatcher.CheckAccess())
                action();
            else
                dispatcher.BeginInvoke(action);
        }

        /// <summary>
        /// Récupére un bitmap pour en faire un BitmapSource qui sera ensuite affiché
        /// </summary>
        /// <param name="source"><see cref="Bitmap"/> à convertir</param>
        /// <returns><see cref="BitmapSource"/> converti</returns>
        public static BitmapSource ToBitmapImage(this System.Drawing.Bitmap source)
        {
            IntPtr ip = source.GetHbitmap();
            BitmapSource bs = null;
            try
            {
                bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(ip,
                   IntPtr.Zero, Int32Rect.Empty,
                   System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
            }
            finally
            {
                DeleteObject(ip);
            }

            return bs;
        }

        /// <summary>
        /// Récupére le tableau de bytes qui définie l'image
        /// </summary>
        /// <param name="bitmap"><see cref="BitmapSource"/> à convertir</param>
        /// <returns><see cref="Array"/> de <see cref="byte"/> du <see cref="BitmapSource"/></returns>
        public static byte[] GetBytesFromBitmapSource(this BitmapSource bitmap)
        {
            byte[] data;
            var encoder = new JpegBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(bitmap));
            using (var ms = new MemoryStream())
            {
                encoder.Save(ms);
                data = ms.ToArray();
            }
            encoder = null;
            return data;
        }

        /// <summary>
        /// Permet de supprimer tous les éléments d'une observablecollection suivant une condition
        /// </summary>
        /// <param name="coll"><see cref="ObservableCollection{T}"/> à traiter</param>
        /// <param name="condition">Predicate de rechercher</param>
        /// <typeparam name="T"><see cref="Type"/> de la collection</typeparam>
        /// <returns>Nombre d'éléments supprimés</returns>
        public static int RemoveAll<T>(this ObservableCollection<T> coll, Func<T, bool> condition)
        {
            var itemsToRemove = coll.Where(condition).ToList();

            foreach (var itemToRemove in itemsToRemove)
            {
                coll.Remove(itemToRemove);
            }

            return itemsToRemove.Count;
        }

        /// <summary>
        /// Permet de retrouver une méthode définie sur une classe
        /// </summary>
        /// <param name="type"><see cref="Type"/> de l'objet</param>
        /// <param name="methodName">Nom de la méthode à chercher</param>
        /// <returns><see cref="MethodInfo"/> de la méthode retrouvée</returns>
        public static MethodInfo GetMethodToLoad(this Type type, string methodName)
        {
            try
            {
                const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.ExactBinding;
                var method = type.GetMethod(methodName, flags, null, null, null);

                if (method == null && type.BaseType != null)
                {
                    //Recherche dans la classe parente
                    var methodInherited = type.BaseType.GetMethod(methodName, flags, null, null, null);
                    return methodInherited;
                }
                return method;
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// Permet de cloner une liste d'objets implémentant ICloneable
        /// </summary>
        /// <typeparam name="T"><see cref="Type"/> de la <see cref="List{T}"/></typeparam>
        /// <param name="list"><see cref="List{T}"/> à cloner</param>
        /// <returns>Clone <see cref="List{T}"/></returns>
        public static List<T> Clone<T>(this List<T> list) where T : ICloneable
        {
            return list.Select(item => (T)item.Clone()).ToList();
        }

        /// <summary>
        /// Permet de cloner une ObservableCollection d'objets implémentant ICloneable
        /// </summary>
        /// <typeparam name="T"><see cref="Type"/> de l'<see cref="ObservableCollection{T}"/></typeparam>
        /// <param name="list"><see cref="ObservableCollection{T}"/> à cloner</param>
        /// <returns>Clone de l'<see cref="ObservableCollection{T}"/></returns>
        public static ObservableCollection<T> Clone<T>(this ObservableCollection<T> list) where T : ICloneable
        {
            return new ObservableCollection<T>(list.Select(item => (T)item.Clone()));
        }

        /// <summary>
        /// Permet de cloner un objet en binary
        /// </summary>
        /// <param name="obj">Objet à cloner</param>
        /// <typeparam name="T"><see cref="Type"/> de l'objet</typeparam>
        /// <returns>Objet cloné</returns>
        public static T DeepClone<T>(T obj)
        {
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                ms.Position = 0;

                return (T)formatter.Deserialize(ms);
            }
        }


        /// <summary>
        /// Permet d'ajouter ou de modifier une clé et une valeur
        /// </summary>
        /// <param name="dictionary">Dictionnaire</param>
        /// <param name="key">Clé à vérifier</param>
        /// <param name="value">Valeur à ajouter ou modifier</param>
        /// <typeparam name="T">Type de la clé</typeparam>
        /// <typeparam name="TO">Type de la valeur</typeparam>
        public static void AddOrReplace<T, TO>(this Dictionary<T, TO> dictionary, T key, TO value)
        {
            if (dictionary.ContainsKey(key))
            {
                dictionary[key] = value;
            }
            else
            {
                dictionary.Add(key, value);
            }
        }

        /// <summary>
        /// Permet d'ajouter une valeur ou bien de modifier la valeur existante en faisant un incrément de l'actuelle
        /// </summary>
        /// <param name="dictionary">Dictionnaire</param>
        /// <param name="key">Clé de type chaine de caractéres</param>
        /// <param name="value">Valeur de type int</param>
        public static void AddOrReplaceWithIncrement(this Dictionary<string, int> dictionary, string key, int value)
        {
            if (dictionary.ContainsKey(key))
            {
                var newValue = dictionary[key] + value;
                dictionary[key] = newValue;
            }
            else
            {
                dictionary.Add(key, value);
            }
        }
    }
}
